<template>
  <component ref="formEl" :is="formRegister ? 'w-form-element' : 'div'"
    v-bind="formRegister && { validators, inputValue, disabled, readonly, isFocused }" v-model:valid="valid"
    @reset="$emit('update:modelValue', (inputValue = ''));$emit('input', '');" :class="classes">
    <input v-if="type === 'hidden'" type="hidden" :name="name || null" v-model="inputValue" />
    <template v-else>
      <template v-if="labelPosition === 'left'">
        <label class="w-input__label w-input__label--left w-form-el-shakable" v-if="$slots.default"
          :for="`w-input--${_.uid}`">
          <slot></slot>
        </label>
        <label class="w-input__label w-input__label--left w-form-el-shakable" v-else-if="label"
          :for="`w-input--${_.uid}`" v-html="label">
        </label>
      </template>
      <div class="w-input__input-wrap" :class="inputWrapClasses">
        <w-icon class="w-input__icon w-input__icon--inner-left" v-if="innerIconLeft" tag="label"
          :for="`w-input--${_.uid}`" @click="$emit('click:inner-icon-left', $event)">{{ innerIconLeft }}</w-icon>
        <input class="w-input__input" v-model="inputValue" v-on="listeners" @input="onInput" @focus="onFocus"
          @blur="onBlur" :id="`w-input--${_.uid}`" :type="type" :name="inputName" :placeholder="placeholder || null"
          :step="step || null" :min="min || null" :max="max || null" :minlength="minlength || null"
          :maxlength="maxlength || null" :readonly="readonly || null" :aria-readonly="readonly ? 'true' : 'false'"
          :disabled="disabled || null" :required="required || null" v-bind="attrs" />
        <template v-if="labelPosition === 'inside' && showLabelInside">
          <label class="w-input__label w-input__label--inside w-form-el-shakable" v-if="$slots.default"
            :for="`w-input--${_.uid}`"
            :class="isFocused && {[valid === false ? 'error' : color]: color || valid === false,}">
            <slot></slot>
          </label>
          <label class="w-input__label w-input__label--inside w-form-el-shakable" v-else-if="label"
            :for="`w-input--${_.uid}`" v-html="label" :class="
              isFocused && {
                [valid === false ? 'error' : color]: color || valid === false,
              }">
          </label>
        </template>
        <w-icon class="w-input__icon w-input__icon--inner-right" v-if="innerIconRight" tag="label"
          :for="`w-input--${_.uid}`" @click="$emit('click:inner-icon-right', $event)">{{ innerIconRight }}</w-icon>
      </div>
      <template v-if="labelPosition === 'right'">
        <label class="w-input__label w-input__label--right w-form-el-shakable" v-if="$slots.default"
          :for="`w-input--${_.uid}`">
          <slot></slot>
        </label>
        <label class="w-input__label w-input__label--right w-form-el-shakable" v-else-if="label"
          :for="`w-input--${_.uid}`" v-html="label">
        </label>
      </template>
    </template>
  </component>
</template>

<script>
  /**
   * @todo Share the common parts between w-input, w-textarea & w-select.
   **/

  import { ref, computed, getCurrentInstance, watch } from "vue";
  export default {
    name: "w-input",
    inject: { formRegister: { default: null } },
    props: {
      modelValue: { default: "" },
      type: { type: String, default: "text" },
      label: { type: String },
      labelPosition: { type: String, default: "inside" },
      innerIconLeft: { type: String },
      innerIconRight: { type: String },
      // When label is inside, allows to move the label above on focus or when filled.
      staticLabel: { type: Boolean },
      placeholder: { type: String },
      color: { type: String, default: "primary" },
      bgColor: { type: String },
      minlength: { type: [Number, String] },
      maxlength: { type: [Number, String] },
      step: { type: [Number, String] },
      min: { type: [Number, String] },
      max: { type: [Number, String] },
      dark: { type: Boolean },
      outline: { type: Boolean },
      round: { type: Boolean },
      shadow: { type: Boolean },
      tile: { type: Boolean },
      // Also name, disabled, readonly, required and validators in the mixin.
      name: { type: String }, // When sending data through form.
      disabled: { type: Boolean },
      readonly: { type: Boolean },
      required: { type: Boolean },
      validators: { type: Array },
    },

    emits: [
      "input",
      "update:modelValue",
      "focus",
      "blur",
      "click:inner-icon-left",
      "click:inner-icon-right",
    ],
    setup(props, ctx) {
      const instance = getCurrentInstance();
      const valid = ref(null);
      const inputValue = ref(props.modelValue);
      // In case of incorrect input type="number", the inputValue gets emptied,
      // and the label would come back on top of the input text.
      const inputNumberError = ref(false);
      const isFocused = ref(false);

      const listeners = computed(() => {
        // Remove the events that are fired separately, so they don't fire twice.
        // eslint-disable-next-line no-unused-vars
        const { input, focus, blur, ...listeners } = ctx.attrs;
        return listeners;
      });
      const attrs = computed(() => {
        // eslint-disable-next-line no-unused-vars
        const { class: Class, ...htmlAttrs } = ctx.attrs;
        return htmlAttrs;
      });
      const hasValue = computed(() => {
        return (
          inputValue.value ||
          ["date", "time"].includes(props.type) ||
          (props.type === "number" && inputNumberError.value)
        );
      });
      const hasLabel = computed(() => {
        return props.label || ctx.slots.default;
      });
      const showLabelInside = computed(() => {
        return !props.staticLabel || (!hasValue.value && !props.placeholder);
      });
      const classes = computed(() => {
        return {
          "w-input": true,
          "w-input--disabled": props.disabled,
          "w-input--readonly": props.readonly,
          [`w-input--${hasValue.value ? "filled" : "empty"}`]: true,
          "w-input--focused": isFocused.value,
          "w-input--dark": props.dark,
          "w-input--floating-label":
            hasLabel.value &&
            props.labelPosition === "inside" &&
            !props.staticLabel &&
            !(props.readonly && !hasValue.value),
          "w-input--no-padding":
            !props.outline && !props.bgColor && !props.shadow && !props.round,
          "w-input--has-placeholder": props.placeholder,
          "w-input--inner-icon-left": props.innerIconLeft,
          "w-input--inner-icon-right": props.innerIconRight,
        };
      });
      const inputWrapClasses = computed(() => {
        return {
          [valid.value === false ? "error" : props.color]:
            props.color || valid.value === false,
          [`${props.bgColor}--bg`]: props.bgColor,
          "w-input__input-wrap--round": props.round,
          "w-input__input-wrap--tile": props.tile,
          // Box adds a padding on input. If there is a bgColor or shadow, a padding is needed.
          "w-input__input-wrap--box":
            props.outline || props.bgColor || props.shadow,
          "w-input__input-wrap--underline": !props.outline,
          "w-input__input-wrap--shadow": props.shadow,
          "w-input__input-wrap--no-padding":
            !props.outline && !props.bgColor && !props.shadow && !props.round,
        };
      });
      const inputName = computed(() => {
        return (
          props.name || `${instance.type.name}--${instance.type.uid || instance.uid}`
        );
      });
      function onInput(e) {
        inputNumberError.value = e.target.validity.badInput; // For input type number.
        ctx.emit("update:modelValue", inputValue.value);
        ctx.emit("input", inputValue.value);
      }
      function onFocus(e) {
        isFocused.value = true;
        ctx.emit("focus", e);
      }
      function onBlur(e) {
        isFocused.value = false;
        ctx.emit("blur", e);
      }

      watch(
        () => props.modelValue,
        (value) => {
          inputValue.value = value;
        }
      );
      return {
        valid,
        inputValue,
        inputNumberError,
        isFocused,
        // computed
        listeners,
        attrs,
        hasValue,
        hasLabel,
        showLabelInside,
        classes,
        inputWrapClasses,
        inputName,
        // methods
        onInput,
        onFocus,
        onBlur,
      };
    },
  };
</script>